package css.sword.util;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.io.IOUtils;
import org.apache.http.*;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.AuthSchemes;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.*;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HttpContext;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.ssl.TrustStrategy;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.*;

import java.io.*;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.*;


public class HttpClientUtils {

    /**
     * @description 最大重试次数
     * @value value:maxRetryTimes
     */
    public static int maxRetryTimes = 5;

    /**
     * @description 字段功能描述
     * @value value:log
     */
    public static Logger log = LoggerFactory.getLogger(HttpClientUtils.class);
    /**
     * @description 字段功能描述
     * @value value:mapper
     */
//    protected static ObjectMapper mapper = new ObjectMapper();
    /**
     * @description 单例
     * @value value:HttpClientUtil
     */
    private static HttpClientUtils httpClientUtil = new HttpClientUtils();
    /**
     * @description 单例对象
     * @value value:httpClient
     */
    private CloseableHttpClient httpClient;

    /**
     *@constructor 构造方法
     */
    private HttpClientUtils() {
        final ConnectionSocketFactory plainsf = PlainConnectionSocketFactory.getSocketFactory();
        SSLConnectionSocketFactory sslConnectionSocketFactory = null;
        try {
            final SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(null, new TrustStrategy() {
                @Override
                public boolean isTrusted(X509Certificate[] x509Certificates, String authType) throws CertificateException {
                    return true;
                }
            }).build();

            sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext, new String[] { "TLSv1", "TLSv1.1", "TLSv1.2" }, null, NoopHostnameVerifier.INSTANCE);
            final Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory> create().register("http", plainsf).register("https", sslConnectionSocketFactory).build();
            final RequestConfig requestConfig = RequestConfig.custom().setConnectionRequestTimeout(5000).setSocketTimeout(900000).setConnectTimeout(5000).build();
            final PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(registry);
            // 将最大连接数增加到200
            cm.setMaxTotal(3000);
            // 将每个路由基础的连接增加到20
            cm.setDefaultMaxPerRoute(1500);

            // 将目标主机的最大连接数增加到50
            final HttpHost localhost = new HttpHost("https://127.0.0.1", 8883);
//            final HttpHost localhost = new HttpHost(ConfigReadUtils.getValue("szoa_fileServiceUrl"), 443);
            cm.setMaxPerRoute(new HttpRoute(localhost), 2000);

            //请求重试处理
            final HttpRequestRetryHandler httpRequestRetryHandler = new HttpRequestRetryHandler() {
                /**
                 *@name    默认重试机制
                 *@description 当连接断开时默认请求重试机制
                 *@time    创建时间:2018年9月7日下午1:58:27
                 *@param exception exception
                 *@param executionCount executionCount
                 *@param context context
                 *@return boolean
                 * @author   廖胜波
                 *@history 修订历史（历次修订内容、修订人、修订时间等）
                 */
                public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
                    
                    if (executionCount >= maxRetryTimes) {// 如果已经重试了5次，就放弃
                        return false;
                    } 
                    if (exception instanceof NoHttpResponseException) {// 如果服务器丢掉了连接，那么就重试
                        return true;
                    } 
                    if (exception instanceof SSLHandshakeException ||
                            exception instanceof InterruptedIOException ||
                            exception instanceof UnknownHostException ||
                            exception instanceof ConnectTimeoutException) {// 不要重试SSL握手异常
                        return false;
                    }

                    if (exception instanceof SSLException) {// ssl握手异常
                        return false;
                    }

                    final HttpClientContext clientContext = HttpClientContext.adapt(context);
                    final HttpRequest request = clientContext.getRequest();
                    // 如果请求是幂等的，就再次尝试
                    if (!(request instanceof HttpEntityEnclosingRequest)) {
                        return true;
                    }
                    return false;
                }
            };
            httpClient = HttpClients.custom().setConnectionManager(cm).setRetryHandler(httpRequestRetryHandler).setDefaultRequestConfig(requestConfig).build();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (KeyStoreException e) {
            e.printStackTrace();
        } catch (KeyManagementException e) {
            e.printStackTrace();
        }
    }

    /**
     *
     * @package com.css.sword.extensions.fileserver.utils
     * @file HttpClientUtils.java 创建时间:2018年9月7日上午11:53:46
     * @title 标题（要求能简洁地表达出类的功能和职责）

     *
     */
    public interface HttpClientDownLoadProgress {
        /**
         * 
         *@name    中文名称
         *@description 相关说明
         *@time    创建时间:2018年9月3日上午10:43:25
         *@param progress progress
         *@author   刘江明
         *@history 修订历史（历次修订内容、修订人、修订时间等）
         */
        void onProgress(int progress);
    }

    /**
     *@name    中文名称
     *@description 相关说明
     *@time    创建时间:2018年9月7日上午11:53:22
     *@return CloseableHttpClient c
     *@author   廖胜波
     *@history 修订历史（历次修订内容、修订人、修订时间等）
     */
    public CloseableHttpClient getHttpClient() {
        return httpClient;
    }

    /**
     *@name    中文名称
     *@description 相关说明
     *@time    创建时间:2018年9月7日上午11:53:01
     *@return HttpClientUtils
     *@author   刘江明
     *@history 修订历史（历次修订内容、修订人、修订时间等）
     */
    public static HttpClientUtils getInstance() {
        return httpClientUtil;
    }

    /**
     *@name    设置httpClient请求参数
     *@description 设置httpClient请求参数
     *@time    创建时间:2018年9月7日上午11:52:13
     *@param httpRequestBase httpRequestBase
     *@author   廖胜波
     *@history 修订历史（历次修订内容、修订人、修订时间等）
     */
    public static void config(HttpRequestBase httpRequestBase) {
        httpRequestBase.setHeader("User-Agent", "Mozilla/5.0");
        httpRequestBase.setHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
        httpRequestBase.setHeader("Accept-Language", "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3");//"en-US,en;q=0.5");
        httpRequestBase.setHeader("Accept-Charset", "ISO-8859-1,utf-8,gbk,gb2312;q=0.7,*;q=0.7");
        // 配置请求的超时设置
        final RequestConfig requestConfig = RequestConfig.custom().setCookieSpec(CookieSpecs.STANDARD_STRICT).setExpectContinueEnabled(Boolean.TRUE)
                .setTargetPreferredAuthSchemes(Arrays.asList(AuthSchemes.NTLM, AuthSchemes.DIGEST)).setProxyPreferredAuthSchemes(Arrays.asList(AuthSchemes.BASIC)).setConnectionRequestTimeout(5000)
                .setSocketTimeout(900000).setConnectTimeout(5000).build();
        httpRequestBase.setConfig(requestConfig);
    }

    /**
     *@name     下载文件
     *@description 下载文件服务器文件
     *@time    创建时间:2018年9月7日上午11:52:03
     *@param url u
     *@param filePath f
     *@param headMap m
     *@author   刘江明
     *@history 修订历史（历次修订内容、修订人、修订时间等）
     */
    public void httpDownloadFile(String url, String filePath, Map<String, String> headMap) {
        try {
            final HttpGet request = new HttpGet(url);
            config(request);
            if (headMap != null) {
                for (Map.Entry<String, String> e : headMap.entrySet()) {
                    request.addHeader(e.getKey(), e.getValue());
                }
            }
            final CloseableHttpResponse response1 = httpClient.execute(request);
            try {
                log.debug(response1.getStatusLine().toString());
                final HttpEntity httpEntity = response1.getEntity();
                final long contentLength = httpEntity.getContentLength();
                final InputStream in = httpEntity.getContent();
                // 根据InputStream 下载文件
                final FileOutputStream fos = new FileOutputStream(filePath);
                // 通过ioutil 对接输入输出流，实现文件下载
                IOUtils.copy(new BufferedInputStream(in), fos);
                fos.close();
                EntityUtils.consume(httpEntity);
            } finally {
                response1.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    
    /**
     *@name    返回文件流
     *@description 返回文件流
     *@time    创建时间:2018年9月29日下午5:10:50
     *@param url r
     *@param headMap f
     *@return InputStream
     *@author   廖胜波
     *@history 修订历史（历次修订内容、修订人、修订时间等）
     */
    public InputStream httpDownloadFileInputStream(String url, Map<String, String> headMap) {
        try {
            final HttpGet request = new HttpGet(url);
            config(request);
            if (headMap != null) {
                for (Map.Entry<String, String> e : headMap.entrySet()) {
                    request.addHeader(e.getKey(), e.getValue());
                }
            }
            final CloseableHttpResponse response1 = httpClient.execute(request);
            try {
                log.debug(response1.getStatusLine().toString());
                final HttpEntity httpEntity = response1.getEntity();
                final long contentLength = httpEntity.getContentLength();
                final InputStream in = httpEntity.getContent();
                // 根据InputStream 下载文件
                return in;
            } finally {
                response1.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    
    /**
     *@name    下载文件返回byte数组
     *@description 下载文件返回byte数组
     *@time    创建时间:2018年9月12日上午10:12:41
     *@param url url
     *@param headMap map
     *@return byte[]
     *@author   廖胜波
     *@history 修订历史（历次修订内容、修订人、修订时间等）
     */
    public byte[] httpDownloadFileByte(String url, Map<String, String> headMap) {
        try {
            final HttpGet request = new HttpGet(url);
            config(request);
            if (headMap != null) {
                for (Map.Entry<String, String> e : headMap.entrySet()) {
                    request.addHeader(e.getKey(), e.getValue());
                }
            }
            final CloseableHttpResponse response1 = httpClient.execute(request);
            try {
                log.debug(response1.getStatusLine().toString());
                final HttpEntity httpEntity = response1.getEntity();
                final InputStream in = httpEntity.getContent();
                // 根据InputStream 下载文件
                final ByteArrayOutputStream output = new ByteArrayOutputStream();
                IOUtils.copy(new BufferedInputStream(in), output);
                return output.toByteArray();

            } finally {
                response1.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    
    
    /**
     * 
     *@name    下载文件
     *@description 获取文件服务器文件
     *@time    创建时间:2018年9月3日上午10:45:59
     *@param url url
     *@param filePath filePath
     *@param progress progress
     *@param headMap headMap
     *@author   刘江明
     *@history 修订历史（历次修订内容、修订人、修订时间等）
     */
    public void httpDownloadFile(String url, String filePath, HttpClientDownLoadProgress progress, Map<String, String> headMap) {

        try {
            final HttpGet request = new HttpGet(url);
            if (headMap != null) {
                for (Map.Entry<String, String> e : headMap.entrySet()) {
                    request.addHeader(e.getKey(), e.getValue());
                }
            }
            final CloseableHttpResponse response1 = httpClient.execute(request);
            try {
                final HttpEntity httpEntity = response1.getEntity();

                final long contentLength = httpEntity.getContentLength();
                final InputStream is = httpEntity.getContent();
                // 根据InputStream 下载文件
                final ByteArrayOutputStream output = new ByteArrayOutputStream();
                final byte[] buffer = new byte[4096];
                int r = 0;
                long totalRead = 0;
                while ((r = is.read(buffer)) > 0) {
                    output.write(buffer, 0, r);
                    totalRead += r;
                    if (progress != null) {// 回调进度
                        progress.onProgress((int) (totalRead * 100 / contentLength));
                    }
                }
                final FileOutputStream fos = new FileOutputStream(filePath);
                output.writeTo(fos);
                output.flush();
                output.close();
                fos.close();
                EntityUtils.consume(httpEntity);
            } finally {
                response1.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     *@name    http get请求
     *@description http get请求
     *@time    创建时间:2018年9月7日上午11:51:05
     *@param url u
     *@param headMap h
     *@return String
     *@author   刘江明
     *@history 修订历史（历次修订内容、修订人、修订时间等）
     */
    public String httpGet(String url, Map<String, String> headMap) {
        String responseContent = null;
        try {
            final HttpGet request = new HttpGet(url);
            if (headMap != null) {
                for (Map.Entry<String, String> e : headMap.entrySet()) {
                    request.addHeader(e.getKey(), e.getValue());

                }
            }
            final CloseableHttpResponse response1 = httpClient.execute(request);
            try {
                final HttpEntity entity = response1.getEntity();
                responseContent = EntityUtils.toString(entity, "UTF-8");
                EntityUtils.consume(entity);
            } finally {
                response1.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return responseContent;
    }

    /**
     *@name    缓冲区
     *@description 缓冲区
     *@time    创建时间:2018年9月7日上午11:50:10
     *@param entity e
     *@return String
     *@throws Exception
     *@author   刘江明
     *@history 修订历史（历次修订内容、修订人、修订时间等）
     */
    private String getRespString(HttpEntity entity) throws Exception {
        if (entity == null) {
            return null;
        }
        final InputStream is = entity.getContent();
        final StringBuffer strBuf = new StringBuffer();
        final byte[] buffer = new byte[4096];
        int r = 0;
        while ((r = is.read(buffer)) > 0) {
            strBuf.append(new String(buffer, 0, r, "UTF-8"));
        }
        return strBuf.toString();
    }

    /**
     *@name   http的post请求
     *@description  http的post请求
     *@time    创建时间:2018年9月7日上午11:49:41
     *@param url u
     *@param paramsMap p
     *@param headMap p
     *@return String
     *@author   刘江明
     *@history 修订历史（历次修订内容、修订人、修订时间等）
     */
    public String httpPost(String url, Map<String, String> paramsMap, Map<String, String> headMap) {
        String responseContent = null;
        try {
            final HttpPost httpPost = new HttpPost(url);
            for (Map.Entry<String, String> e : headMap.entrySet()) {
                httpPost.addHeader(e.getKey(), e.getValue());
            }
            if (paramsMap != null) {
                final List<NameValuePair> nameValuePairList = new ArrayList<NameValuePair>();

                for (String key : paramsMap.keySet()) {
                    nameValuePairList.add(new BasicNameValuePair(key, paramsMap.get(key)));
                }
                final UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(nameValuePairList, "utf-8");
                formEntity.setContentType("application/x-www-form-urlencoded; charset=UTF-8");
                httpPost.setEntity(formEntity);
                // request.setEntity(new ByteArrayEntity(body));
                // request.setEntity(new StringEntity(body, "utf-8"));
            }

            final CloseableHttpResponse response = httpClient.execute(httpPost);
            try {
                final HttpEntity entity = response.getEntity();
                responseContent = getRespString(entity);
                EntityUtils.consume(entity);
            } finally {
                response.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return responseContent;
    }


    /**
     *@name    上传文件
     *@description 上传文件到文件服务器
     *@time    创建时间:2018年10月13日下午2:02:54
     *@param serverUrl 服务器地址
     *@param localFilePath  本地文件路径
     *@param serverFieldName serverFieldName
     *@param params  params
     *@return  String
     *@throws Exception
     *@author   廖胜波
     *@history 修订历史（历次修订内容、修订人、修订时间等）
     */
    public String uploadFileImpl(String serverUrl, String localFilePath, String serverFieldName, Map<String, String> params) throws Exception {
        String respStr = null;
        CloseableHttpResponse response = null;
        try {
            final HttpPost httppost = new HttpPost(serverUrl);
            final FileBody binFileBody = new FileBody(new File(localFilePath), ContentType.MULTIPART_FORM_DATA);

            final MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create().setMode(HttpMultipartMode.RFC6532);
            multipartEntityBuilder.setCharset(Consts.UTF_8);
            // add the file params
            multipartEntityBuilder.addPart(serverFieldName, binFileBody);
            // 设置上传的其他参数
            setUploadParams(multipartEntityBuilder, params);

            final HttpEntity reqEntity = multipartEntityBuilder.build();
            httppost.setEntity(reqEntity);

            response = httpClient.execute(httppost);

            final HttpEntity resEntity = response.getEntity();
            respStr = EntityUtils.toString(resEntity, "UTF-8");
            EntityUtils.consume(resEntity);
        } finally {
            if (response != null) {
                response.close();
            }
        }

        return respStr;
    }

    /**
     *@name    上传文件
     *@description 上传文件到文件服务器
     *@time    创建时间:2018年10月13日下午2:02:54
     *@param serverUrl 服务器地址
     *@param fileExt 文件扩展名（.doc  .jpg）等
     *@param ins  本地文件路径
     *@param serverFieldName serverFieldName
     *@param params  params
     *@return  String
     *@throws Exception
     *@author   廖胜波
     *@history 修订历史（历次修订内容、修订人、修订时间等）
     */
    public String uploadFileImpl(String serverUrl, InputStream  ins,
                                 String serverFieldName,
                                 Map<String, String> params,String fileExt) throws Exception {
        String respStr = null;
        CloseableHttpResponse response = null;
        File tempFile = new File(
            System.getProperty("java.io.tmpdir") + "/upload_tmp" + UUID.randomUUID()+fileExt);
        try {
            final HttpPost httppost = new HttpPost(serverUrl);


            OutputStream os = new FileOutputStream(tempFile);
            int bytesRead = 0;
            byte[] buffer = new byte[8192];
            while ((bytesRead = ins.read(buffer, 0, 8192)) != -1) {
                os.write(buffer, 0, bytesRead);
            }
            os.close();
            ins.close();
            final FileBody binFileBody = new FileBody(tempFile, ContentType.MULTIPART_FORM_DATA);

            final MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create().setMode(HttpMultipartMode.RFC6532);
            multipartEntityBuilder.setCharset(Consts.UTF_8);
            // add the file params
            multipartEntityBuilder.addPart(serverFieldName, binFileBody);
            // 设置上传的其他参数
            setUploadParams(multipartEntityBuilder, params);

            final HttpEntity reqEntity = multipartEntityBuilder.build();
            httppost.setEntity(reqEntity);

            response = httpClient.execute(httppost);

            final HttpEntity resEntity = response.getEntity();
            respStr = EntityUtils.toString(resEntity, "UTF-8");
            EntityUtils.consume(resEntity);
        } finally {
            if (response != null) {
                response.close();
//                tempFile.delete();
            }
        }

        return respStr;
    }



    /**
     *@name    设置上传文件时所附带的其他参数
     *@description 设置上传文件时所附带的其他参数
     *@time    创建时间:2018年10月13日下午2:02:22
     *@param multipartEntityBuilder multipartEntityBuilder
     *@param params  params
     *@author   廖胜波
     *@history 修订历史（历次修订内容、修订人、修订时间等）
     */
    private void setUploadParams(MultipartEntityBuilder multipartEntityBuilder,
                                 Map<String, String> params) {
        if (params != null && params.size() > 0) {
            final Set<String> keys = params.keySet();
            for (String key : keys) {
                multipartEntityBuilder
                        .addPart(key, new StringBody(params.get(key),
                                ContentType.create("text/plain", Consts.UTF_8)));
            }
        }
    }




}